import glob, shutil, os, re, logging
from .base_function.norm_filename import sanitize_filename
import json
from toolbox import update_ui, trimmed_format_exc, gen_time_str
from toolbox import CatchException, report_exception, get_log_folder
from toolbox import write_history_to_file, promote_file_to_downloadzone
import time
from .translation_quality_monitor import monitor_translation_quality, monitor_thread_translation_quality, check_translation_completeness, generate_quality_report, evaluate_thread_translations, write_continuity_check_report
from .Markdown_Translate_Preoutline import enhance_translation_prompt_with_outline, get_document_outline, enhance_translation_prompt_with_book_outline, get_book_outline, record_outline_metrics, remove_suspected_outline_blocks_in_slices
fast_debug = False
import os
import re
from typing import Dict

# -------------------------------------------------
# 新增函数：parse_custom_md_files
# -------------------------------------------------
# 负责读取 configs/markdown_translate_settings.json 中的
# custom_md_files 配置，解析相对/绝对路径以及通配符，
# 返回合法的 Markdown 文件列表以及错误信息（若有）。
# -------------------------------------------------
import json
import os
import glob
import logging
from typing import List, Tuple, Optional

def parse_custom_md_files(settings_path: str) -> Tuple[List[str], Optional[str]]:
    """
    解析 `custom_md_files` 配置并返回合法的 Markdown 文件路径列表。

    参数
    ----------
    settings_path: str
        `markdown_translate_settings.json` 的完整路径。

    返回
    ----------
    Tuple[List[str], Optional[str]]
        - 第一个元素：合法的文件路径列表（绝对路径）。
        - 第二个元素：错误信息（若有），否则为 None。
    """
    # -------------------------------------------------
    # 开始解析 custom_md_files 配置
    # -------------------------------------------------
    logging.info(f"开始解析 custom_md_files，配置文件路径: {settings_path}")
    if not os.path.exists(settings_path):
        logging.warning(f"custom_md_files 配置文件未找到: {settings_path}")
        return [], None
    try:
        with open(settings_path, "r", encoding="utf-8") as f:
            settings = json.load(f)
    except Exception as e:
        logging.error(f"读取 custom_md_files 配置失败: {e}")
        return [], f"读取配置文件出错: {e}"
    custom_md = settings.get("custom_md_files", "")
    if not custom_md or not custom_md.strip():
        # 空配置走单文件流程
        logging.debug("custom_md_files 配置为空，使用默认单文件流程")
        return [], None
    
    # 获取 DEFAULT_BATCH_FOLDER 作为相对路径的基础
    default_batch_folder = settings.get("DEFAULT_BATCH_FOLDER", "")
    if not default_batch_folder:
        logging.error("配置文件中未找到 DEFAULT_BATCH_FOLDER")
        return [], "配置文件中未找到 DEFAULT_BATCH_FOLDER"
    
    # 如果 DEFAULT_BATCH_FOLDER 不存在，返回错误
    if not os.path.exists(default_batch_folder):
        logging.error(f"DEFAULT_BATCH_FOLDER 指定的目录不存在: {default_batch_folder}")
        return [], f"DEFAULT_BATCH_FOLDER 指定的目录不存在: {default_batch_folder}"
    
    # 拆分、清理
    raw_patterns = [p.strip() for p in custom_md.split(",") if p.strip()]
    logging.debug(f"解析到的原始模式列表 ({len(raw_patterns)}): {raw_patterns}")
    valid_files: List[str] = []
    errors: List[str] = []
    
    for pat in raw_patterns:
        logging.debug(f"处理模式: '{pat}'")
        # 支持通配符，基于 DEFAULT_BATCH_FOLDER
        try:
            # 处理路径模式
            if os.path.isabs(pat):
                # 绝对路径，直接使用
                search_path = pat
            elif pat.startswith('./'):
                # 相对路径，相对于 DEFAULT_BATCH_FOLDER
                search_path = os.path.join(default_batch_folder, pat[2:])
            else:
                # 普通相对路径，也相对于 DEFAULT_BATCH_FOLDER
                search_path = os.path.join(default_batch_folder, pat)
            
            expanded = glob.glob(search_path, recursive=True)
            logging.debug(f"模式 '{pat}' 扩展结果 ({len(expanded)}): {expanded}")
            
            # 如果没有找到文件，尝试在 DEFAULT_BATCH_FOLDER 的子目录中递归查找
            if not expanded and not os.path.isabs(pat):
                search_path = os.path.join(default_batch_folder, '**', pat)
                expanded = glob.glob(search_path, recursive=True)
                logging.debug(f"递归查找模式 '{pat}' 扩展结果 ({len(expanded)}): {expanded}")
        except Exception as e:
            err_msg = f"解析模式 '{pat}' 时出错: {e}"
            logging.error(err_msg)
            errors.append(err_msg)
            continue
        
        if not expanded:
            err_msg = f"custom_md_files pattern '{pat}' 未匹配到任何文件"
            logging.warning(err_msg)
            errors.append(err_msg)
            continue
        
        for fpath in expanded:
            if not fpath.lower().endswith(".md"):
                err_msg = f"路径 '{fpath}' 不是 Markdown 文件 (.md)"
                logging.warning(err_msg)
                errors.append(err_msg)
                continue
            if not os.path.isfile(fpath):
                err_msg = f"路径 '{fpath}' 不存在或不是文件"
                logging.warning(err_msg)
                errors.append(err_msg)
                continue
            abs_path = os.path.abspath(fpath)
            logging.info(f"解析成功的 Markdown 文件: {abs_path}")
            valid_files.append(abs_path)
    
    # 去重并保持出现顺序
    valid_files = list(dict.fromkeys(valid_files))
    logging.info(f"最终解析得到 {len(valid_files)} 个合法 Markdown 文件")
    error_msg = "\n".join(errors) if errors else None
    if error_msg:
        logging.error(f"解析 custom_md_files 时累计错误:\n{error_msg}")
    return valid_files, error_msg

# 本模块集成了 Markdown_Translate_Preoutline 的大纲提取功能
# 在翻译前，会为整本书籍提取大纲，并将大纲信息添加到每个切片的翻译提示词中
# 这有助于提高翻译质量，使翻译结果更加准确和连贯
# 大纲提取功能由 marko 库实现，需要安装：pip install marko

# 用户可在此设置最大token限制，用于切割长文本
MAX_TOKEN_LIMIT = 1024
# 用户可在此设置翻译工作的并发数量
MAX_WORKERS = 40
# 用户可在此设置大纲最大字符数限制
MAX_OUTLINE_CHARS = 1000
# 默认提示词
DEFAULT_PROMPT = """        
(prompt)
v0.86
一、翻译基础要求：
翻译MD源代码（包含latex源码）的内容翻译为目标语言。默认目标语言为中文。
中文语法，句式要求生动活泼，通俗易懂，句子流畅度要最高。句式温暖亲切，清新自然。
一切没有文采的，不生动的语句逐行润色成生动、活泼的句子。
一切死板、冰冷、复杂晦涩的句式逐行转化为通俗易懂的语句。
一切中文里没有的句式和表达转化为中文本土句式并保证通俗易懂。
仔细思考每一句话在中文里是如何恰当表达的，不要机械的翻译，输出的译文要完全像一个中文本土作者的作品。
只逐句翻译，不要有任何一个字的多余回答，比如解释和说明语句以及英文原文。
段落中的核心要点、重点概念的词语给与加粗显示。不要加粗整个段落，整个句子。
智能断句：太长的不利于阅读的句子要根据语义转换为中文环境中的若干短句组合。断句要完全匹配中文阅读习惯，不需要和外文句式对应。
输出最终翻译：输出的时候只输出最终翻译，不要有任何解释或者说明，否则我最终翻译的书籍中会夹杂太多垃圾信息。
数学公式：一个一个的，一句一句的翻译公式，尽量隔离公式之间的错误。

【特别重要】独立行的LaTeX数学公式处理：
1. 独立行的数学公式是指单独占一行且被$$...$$包围的公式，不需要翻译，直接原样保留
2. 所有$$...$$格式的独立行公式必须保持原样，一字不改地复制到译文中
3. 行内公式（即被单个$符号包围的公式）仍然按照原有方式处理

二、写作技巧（针对非代码和非数学公式段落）：
1、不影响内容和逻辑时，在文章中适当留白，让读者有思考的空间
2、在叙述中分层次表达情感，让情感表达更丰富
3、略微加入幽默元素，让文章更轻松有趣
4、句式要长短结合，徐徐的切换，使得文章不那么生硬。
5、用更简洁的方式传递信息，让句子更加有力
6、删除多余的词语，使表达更加简练
7、把复杂的句子拆开，用短句子表达，更有冲击力
8、用更多动词，让文章更有活力
9、简洁的语言表达复杂的思想，使文章更精炼有力
10、尽量用主动句，增加文章力量
11、全部使用本土句式代替原有句法，不必考虑句式的对应关系。但因果机制要严谨。
12、译文要引发情绪共鸣，增强代入感,但要保证意义等价。全文情感要强烈同时保持一定克制，文笔强烈但不过度夸张。
13、通过双向传递情感，让读者在情感交流中受到感染，保持因果机制要严谨
14、通过句式的长短搭配，使文章读起来有节奏感
15、通过长短句的变化，控制文章的节奏
16、多维度润化译文，增加可读性。但保持不越界。
17、断句要明确，译文中长句子的容忍度为0。断句的优先级是最高的。长句子通通强行拆解为短句的组合，阅读时候要体验出断句的顿挫感，这是简化阅读强度的关键！！
18、量化数据视觉强化
19、情景具象化

三、详细断句规则（针对非代码和非数学公式段落）：
1、用破折号分层，对比句前置，术语精简。
2、冒号引导递进，拆分定中结构，转折限行化。
3、动宾紧凑衔接，逗号强调主语
4、情景状语前置，冒号列举式，
5、动词强化
6、场景具象化
7、精确量化
8、短句点睛
9、比喻显性化
10、14字以上用逗号等分割
11、逗号激活比喻
12、补转折词
13、前置时间；焦点前置
14、动词阶梯递进。
15、多重动词要分割。
16、用破折号显性归因。

对比举例第一批：以下是调整后的表格（已去除模型名称，仅保留版本号）：

| 最佳断句案例 (满分)                                      | 最差断句案例 (缺陷)                                  | 核心差异点                      |
| ------------------------------------------------ | -------------------------------------------- | -------------------------- |
| 近年发现的坏死性凋亡——这种受控坏死形式依赖于RIP1/RIP3蛋白。        | 坏死性凋亡被描述为一种依赖RIP1和/或RIP3的受调控坏死形式。      | ✅ 破折号分层 vs ❌ 30字无停顿长句      |
| 与传统坏死不同，坏死性凋亡的典型特征在于分子事件的有序性。              | 分子事件的控制序列是坏死性凋亡的典型特征。                  | ✅ 对比前置 vs ❌ 英文语序直译         |
| 凋亡通过DNA核小体间断裂及半胱天冬酶激活等特征定义。                | 凋亡通过被称为半胱天冬酶（caspases）的细胞内酶的激活来定义。     | ✅ 术语精简 vs ❌ 22字超长插入语       |
| 研究发现：细胞代谢状态不仅决定细胞生存与否，更能精准调控不同死亡模式的启动。    | 细胞代谢对程序性细胞死亡的调控具有深远影响，尤其对死亡类型具有决定性作用。 | ✅ 冒号引导递进 vs ❌ "尤其"逻辑断裂     |
| 深入解析代谢波动如何通过信号转导通路精准调控不同细胞死亡路径。           | 深入解析代谢波动调控不同细胞死亡通路的信号传导级联反应...        | ✅ 拆分定中结构 vs ❌ 16字超长定语      |
| 值得注意的是，代谢异常与细胞死亡通路失调不仅是肿瘤核心特征，同样参与多种人类疾病。 | 由于代谢失调与细胞死亡通路改变还参与了多种人类疾病的发病机制...      | ✅ 转折显性化 vs ❌ "由于"缺失结果句     |
| 精准解析控制死亡模式的分子事件，对选择性干预这些过程至关重要。            | 由于这些不同形式的程序性细胞死亡在正常和病理条件下均参与重要调控...    | ✅ 动宾紧凑衔接 vs ❌ 连词滥用无重点      |
| 本文特别聚焦于代谢与坏死性凋亡之间的关联机制。                   | 本篇综述将聚焦代谢与程序性坏死的关联机制展开探讨。             | ✅ 主动态单句 vs ❌ "聚焦...展开探讨"重复 |
| 癌细胞的代谢特征图谱，势必对恶性转化的各个阶段产生深远影响。             | 这一知识在医学多个领域具有巨大的转化潜力。                 | ✅ 逗号强调主语 vs ❌ 术语孤立无上下文     |
对比举例第二批：

| **原句核心内容**      | **最佳断句案例**                                        | **最差断句案例**                                    | **优化核心**                               |
| --------------- | ------------------------------------------------- | --------------------------------------------- | -------------------------------------- |
| **信号通路与代谢交汇**   | 细胞死亡信号通路与代谢事件的**深度交织**，正推动治疗新策略的诞生。        | 这种细胞死亡信号通路和代谢事件之间的交叉互动正在被研究用于开发新的治疗方法。 (v0.6) | ✅动词动态化("推动") vs ❌被动化("正在被研究")          |
| **代谢波动响应机制**    | 当**葡萄糖供应骤减**时，细胞通过激活AMPK通路**重编程代谢流向**。     | 细胞在葡萄糖供应减少的情况下通过AMPK通路激活进行代谢重编程。        | ✅情景状语前置 vs ❌30字无停顿长句                   |
| **凋亡的生化标志定义**   | 凋亡的判定需满足**三大标志**：染色质凝集、caspase级联激活、凋亡小体形成。  | 凋亡通过DNA核小体间断裂和被称为半胱天冬酶的蛋白酶激活等参数定义。 (v0.6)     | ✅冒号列举式 vs ❌超长定中结构("被称为...的蛋白酶")        |
| **转化医学潜力说明**    | 这些发现**不仅解锁了肿瘤治疗新靶点**，更为代谢性疾病提供干预思路。        | 由于该机制在多种疾病中发挥作用，因此具有转化潜力。             | ✅动词强化("解锁") vs ❌笼统表述("发挥作用")           |
| **坏死性凋亡的病理意义**  | **炎症风暴中**，坏死性凋亡通过释放DAMPs分子**放大组织损伤**。     | 坏死性凋亡在炎症条件下会造成损伤相关分子模式的释放从而导致组织损伤。 (v0.6)     | ✅场景具象化("炎症风暴") vs ❌术语堆砌无断句             |
| **代谢决定死亡类型的机制** | 线粒体代谢产物乙酰-CoA**累积至临界浓度时**，将直接**触发凋亡程序**。    | 当线粒体代谢产物乙酰辅酶A积累到一定水平会诱导细胞凋亡。          | ✅精确量化("临界浓度") vs ❌模糊表述("一定水平")         |
| **分子事件的有序性强调**  | **关键在有序**：坏死性凋亡严格遵循RIP1-RIP3-MLKL磷酸化级联。    | 坏死性凋亡的典型特征是其分子事件序列受到严格调控。               | ✅短句点睛("关键在有序") vs ❌抽象描述("受到严格调控")      |
| **代谢干预的治疗前景**   | 靶向谷氨酰胺代谢**如同切断敌军粮草**，可有效增敏肿瘤细胞死亡。          | 通过抑制谷氨酰胺代谢途径可以增强肿瘤细胞对死亡的敏感性。           | ✅军事比喻显性化 vs ❌机械直译("通过抑制...途径")         |
| **基础研究的临床价值**   | 这些分子机制的解析，**正为"不可成药"靶点带来破局曙光**。            | 对该机制的深入理解可能为难以靶向治疗的靶点提供新思路。           | ✅创新术语("不可成药") + 动态意象("破局曙光") vs ❌模板化表达 |

| 很差断句                         | 断句问题类型 | 具体缺陷                 | 正确方法       | 正确断句                       |
| ---------------------------------- | ------ | -------------------- | ---------- | --------------------------- |
| 在电离辐射冲击下原子结构重组导致性质不可预测改变           | 科技长句窒息 | 14字无逗号，科技术语粘连        | 在"冲击下"后增逗号 | 在电离辐射冲击下，原子结构重组性质不可控改变      |
| 反复小剂量投药逐渐耗尽乙酰胆碱酯酶储备                | 专业术语梗阻 | 9字专业名词“乙酰胆碱酯酶”未分段    | 拆分为        | 反复微量投药，乙酰胆碱酯酶储备逐渐枯竭         |
| 五氯酚阻断能量代谢链致受害者如同自燃                 | 比喻黏连   | 动作("致")与比喻("自燃")直接黏连 | 增逗号激活比喻    | 五氯酚阻断能量链，受害者如同自燃            |
| 加州公共卫生部声称未发现危害仍下令停用DDD             | 逻辑断层   | "声称"与"下令"因果断裂        | 补转折词       | 加州卫生部虽称无害，仍紧急叫停DDD          |
| 被农田包围的保护区孤岛般共享污染水源                 | 定语吞噬   | 12字定语压缩主语("保护区")     | 破开定语       | 保护区困于农田，如孤岛共饮毒水             |
| 最后一次施药后9个月孵化的新鱼携带毒素                | 时间定语超载 | 9字时间状语压迫主语("新鱼")     | 前置时间       | 停药九月后，新孵化鱼群竟携带毒素            |
| 美狄亚魔袍化作化学药剂在血液埋死亡引信                | 主谓失衡   | 次要信息("在血液")抢夺焦点      | 焦点前置       | 美狄亚魔袍变毒剂，死亡引信藏血脉            |
| 蓄意投毒入水库竟成常态居民唯饮毒或付费                | 制度批判模糊 | 未突出政策荒诞性             | 增破折号       | 投毒入水库竟常态——居民抉择：饮鸩或买命？       |
| 浮游生物含毒5ppm植食鱼蓄40-300ppm肉食鱼达2500ppm | 数据平铺   | 递进感缺失                | 增动词阶梯      | 浮游蓄毒5ppm→植食鱼翻80倍→肉食鱼爆增500倍！ |
| 新生雏鸟破壳数小时入水藏亲鸟羽翼下                  | 动态黏连   | 三重动作未分层              | 时间切割       | 雏鸟破壳，数小时入水，藏亲鸟羽翼下           |

| 很差断句                                                                        | 关键缺陷        | 正确断句                                          | 正确方法                      |
| --------------------------------------------------------------------------- | ----------- | --------------------------------------------- | ------------------------- |
| ​**​“人类首次在地球历史上，每个人都从受孕到死亡，始终暴露在危险化学物质的威胁中。”​**​                            | 时间状语割裂主谓    | “人类首度——从受孕到死亡——被化学威胁全程笼罩。”                    | 破折号封装状语链，强化“笼罩”动词         |
| ​**​“这种污染大多不可逆转——它不仅破坏维系生命的物质基础，更在生物组织中引发不可逆的恶性连锁反应。”​**​                   | “不可逆”重复窒息   | “污染多不可逆：撕裂生命根基，更在生物组织中引爆死亡链式反应。”              | 删除冗余+冒号递进+动词“撕裂/引爆”升级     |
| ​**​“砷毒理学权威萨特李博士指出，尽管有机杀虫剂已取代砷制剂，烟草植株仍持续吸收旧毒素——烟草种植土壤已饱含铅砷酸盐这类难溶性剧毒残留。”​**​ | 破折号滥用导致因果断裂 | “萨特李博士揭穿残酷现实：有机药虽替砷剂，烟草仍吸毒！根源——土壤早被铅砷酸盐永久毒害。” | 叹号强化矛盾+独立短句“根源...”显性归因    |
| ​**​“被土壤和地表水被农药等化学物质污染，不仅可能引入毒物，更可能掺入致癌物质。”​**​                             | 双“被”字导致语态混乱 | 土壤与地表水被污染后——农药不止可能引入毒物，更可能参入致癌物！              | 删除被动标记+破折号分层+动词“沦陷/暗渡”具象化 |
| ​**​“未来世代恐怕难以宽恕我们对支撑万物生息的自然完整性的草率漠视。”​**​                                   | 定语堆叠阻塞情感传递  | 未来子孙的审判将至——我们，竟漠视自然完整性！这万物生息的根基...            | 破折号切割主客立场+叹号强化罪状+省略号留白    |

四、排版，格式要求：
appendix也要翻译。
去掉英文原文：输出翻译时不要带任何依据的英文原文。
输出的纯净性：保证输出的文本只包含纯净的译文，不输出其他任何译文之外的文本。
翻译完整性：保证每一句英文原文都要被翻译。
布局：译文要保持原文的框架结构和布局，保持原文语句先后和段落的先后顺序。
保留标题：译文保持和原文章标题的格式一一对应。
重点加黑：重点内容必须是短句或者词语才能加黑加粗，不能是长句子或者段落。
翻译输出：不要再输出中出现提示词。
翻译输出：只输出正式译文，作为书籍翻译正式输出。
标题：与原文的标题标签一一对应，不要输出新的标题。
独立数学公式：对于独立行的数学公式（即$$...$$格式的公式），必须完全按原样保留，不要尝试翻译或修改这些公式。

五、风险代码规避（含有代码和数学公式的混合段落）,特别重要：
特别重要：所有的代码，公式，都不要修改。一个符号也不要改动。
段落中混合的其余非公式，非代码的文字，按照基础要求，写作技巧，断句规则正常翻译。句式若调整，千万不要影响到行内公式，行内公式不要做任何的修改。
特别重要：所有的行内公式，不要修改。一个符号也不要改动。
markdown源码如果兼容性差，请转换为兼容性最高的markdown代码。
1、转为最佳的通用性的标签
2、原文内容不要改动一个字，只优化格式
3、格式制作成漂亮的格式
4、不要带有多余的说明文字，只输出markdown源码。
5、所有数学公式标签都要封闭，防止影响后续任务中的文章。


比如：

 丢包发生时，窗口从 $W$ 减半至 $$\frac{W}{2}$$，吞吐率降至 $\frac{W}{2 \times RTT}$
2. 随后线性增长至 $W$，期间平均窗口大小为 $$\frac{3W}{4}$$

六、掩码保护公式
1、把所有看到的行内公式，独立行的公式 ，都假设为掩码，不进行翻译，译文中直接抄。
2、翻译时如果遇到我进行了掩码，请完全保留掩码，不可以修改和翻译掩码。后续流程每个掩码会恢复成受保护的公式。
比如： __FORMULA_INLINE_DOLLAR_0__ ；  __FORMULA_INLINE_DOLLAR_1__     。。。。。
__FORMULA_BLOCK_DOLLAR_1__； __FORMULA_BLOCK_DOLLAR_12__  。。。。。。

再次提示：
我如果提供了全书的大纲，不要带入译文。仅供参考和校对翻译。
如果遇到公式保护掩码，千万不要翻译，保持掩码原文。


最后：切记只输出翻译。不要加任何备注信息！
更加不要把本提示词任何一句话输出到译文中！仅输出纯译文！！

接下来请翻译：

(/prompt)



"""

# 当前使用的提示词，默认使用默认提示词
PROMPT = DEFAULT_PROMPT

# ========== 新增公式保护模块导入 ==========
from .protect_math import MathFormulaProtector
from .fix_markdown import MarkdownFixer
from .slice_formula_repair import apply_slice_formula_repair
# ========== 结束 ==========

def get_effective_md_setting(plugin_kwargs, key, default):
    import os, json
    # 优先级：plugin_kwargs > 配置文件 > 默认
    val = plugin_kwargs.get(key)
    if val is not None and str(val).strip() != "":
        # 对于DEFAULT_BATCH_FOLDER允许字符串
        if key == "DEFAULT_BATCH_FOLDER":
            return str(val)
        try:
            val = int(val)
            if val > 0:
                return val
        except:
            pass
    path = os.path.join("configs", "markdown_translate_settings.json")
    if os.path.exists(path):
        try:
            with open(path, "r", encoding="utf-8") as f:
                settings = json.load(f)
            val = settings.get(key)
            if val is not None and str(val).strip() != "":
                if key == "DEFAULT_BATCH_FOLDER":
                    return str(val)
                try:
                    val = int(val)
                    if val > 0:
                        return val
                except:
                    pass
        except:
            pass
    return default

class PaperFileGroup():
    def __init__(self):
        self.file_paths = []
        self.file_contents = []
        self.sp_file_contents = []
        self.sp_file_index = []
        self.sp_file_tag = []
        self.split_method_report = []  # 新增：记录每个文件的切分方法
        # count_token
        from request_llms.bridge_all import model_info
        enc = model_info["gpt-3.5-turbo"]['tokenizer']
        def get_token_num(txt): return len(enc.encode(txt, disallowed_special=()))
        self.get_token_num = get_token_num

    def run_file_split(self, max_token_limit=MAX_TOKEN_LIMIT, prefix=""):
        """
        将长文本分离开来
        """
        for index, file_content in enumerate(self.file_contents):
            if self.get_token_num(file_content) < max_token_limit:
                self.sp_file_contents.append(file_content)
                self.sp_file_index.append(index)
                self.sp_file_tag.append(self.file_paths[index])
                self.split_method_report.append("无需切分")
            else:
                from crazy_functions.pdf_fns.breakdown_txt import breakdown_text_to_satisfy_token_limit
                # 传递prefix参数给breakdown_text_to_satisfy_token_limit函数
                segments, strategy_name = breakdown_text_to_satisfy_token_limit(file_content, max_token_limit, "gpt-3.5-turbo", prefix)
                for j, segment in enumerate(segments):
                    self.sp_file_contents.append(segment)
                    self.sp_file_index.append(index)
                    self.sp_file_tag.append(self.file_paths[index] + f".part-{j}.md")
                self.split_method_report.append(strategy_name)
        logging.info('Segmentation: done')

    def merge_result(self):
        self.file_result = ["" for _ in range(len(self.file_paths))]
        for r, k in zip(self.sp_file_result, self.sp_file_index):
            # 每个切片结尾强制加一个换行
            if not r.endswith('\n'):
                r = r + '\n'
            self.file_result[k] += r

    def write_result(self, language, prefix=''):
        """
        将翻译结果写入文件，并在文件名前添加前缀（若提供），以便区分不同来源的译文。
        """
        manifest = []
        for path, res in zip(self.file_paths, self.file_result):
            import re
            base_dir = os.path.dirname(path)               # 原文件所在目录
            folder_name = os.path.basename(base_dir)       # 目录名（书籍子目录名）
            # 原始文件名（不含扩展名），用于前缀
            original_base = os.path.splitext(os.path.basename(path))[0]
    
            # 目标文件名：<原文件名>-中文翻译.md
            # 如果prefix不为空，检查是否与original_base重复（去除末尾的连字符）
            if prefix:
                # 移除prefix末尾的连字符
                clean_prefix = prefix.rstrip('-')
                # 如果clean_prefix与original_base相同，则只使用original_base
                if clean_prefix == original_base:
                    base_name = f'{original_base}-中文翻译'
                else:
                    base_name = f'{prefix}{original_base}-中文翻译'
            else:
                base_name = f'{original_base}-中文翻译'
                
            index = 1
            dst_file = os.path.join(base_dir, f'{base_name}.md')
            # 防止同名文件冲突，追加序号
            while os.path.exists(dst_file):
                dst_file = os.path.join(base_dir, f'{base_name}-{index}.md')
                index += 1
            with open(dst_file, 'w', encoding='utf8') as f:
                manifest.append(dst_file)
                f.write(res)
    
            # 生成以文件夹名为前缀的副本（保持原有行为）
            valid_folder_name = re.sub(r'[\\/*?"<>|]', '_', folder_name)
            if index > 1:
                folder_file = os.path.join(base_dir, f'{valid_folder_name}-{index}.md')
            else:
                folder_file = os.path.join(base_dir, f'{valid_folder_name}.md')
            with open(folder_file, 'w', encoding='utf8') as f:
                manifest.append(folder_file)
                f.write(res)
        return manifest

def get_files_from_everything(txt, preference=''):
    if txt == "": return False, None, None
    success = True
    if txt.startswith('http'):
        import requests
        from toolbox import get_conf
        proxies = get_conf('proxies')
        # 网络的远程文件
        if preference == 'Github':
            logging.info('正在从github下载资源 ...')
            if not txt.endswith('.md'):
                # Make a request to the GitHub API to retrieve the repository information
                url = txt.replace("https://github.com/", "https://api.github.com/repos/") + '/readme'
                response = requests.get(url, proxies=proxies)
                txt = response.json()['download_url']
            else:
                txt = txt.replace("https://github.com/", "https://raw.githubusercontent.com/")
                txt = txt.replace("/blob/", "/")

        r = requests.get(txt, proxies=proxies)
        download_local = f'{get_log_folder(plugin_name="批量Markdown翻译")}/raw-readme-{gen_time_str()}.md'
        project_folder = f'{get_log_folder(plugin_name="批量Markdown翻译")}'
        with open(download_local, 'wb+') as f: f.write(r.content)
        file_manifest = [download_local]
    elif txt.endswith('.md'):
        # 直接给定文件
        file_manifest = [txt]
        project_folder = os.path.dirname(txt)
    elif os.path.exists(txt):
        # 本地路径，递归搜索
        project_folder = txt
        # 查找根目录下的md文件
        root_md_files = [f for f in glob.glob(f'{project_folder}/*.md')]
        # 查找一级子目录中的full.md文件
        subdir_full_md_files = [f for f in glob.glob(f'{project_folder}/*/full.md')]
        # 合并文件列表
        file_manifest = root_md_files + subdir_full_md_files
        if len(file_manifest) == 0:
            success = False
    else:
        project_folder = None
        file_manifest = []
        success = False

    # ==================== 读取自定义文件列表 ====================
    settings_path = os.path.join("configs", "markdown_translate_settings.json")
    if os.path.exists(settings_path):
        custom_files, err = parse_custom_md_files(settings_path)
        if err:
            logging.error(f"解析 custom_md_files 失败: {err}")
            # 中止翻译流程，返回失败状态
            return False, None, None
        if custom_files:
            # 合并自定义文件列表，去重并保持顺序
            file_manifest = list(dict.fromkeys(file_manifest + custom_files))
            logging.info(f"已合并自定义 Markdown 文件列表，数量: {len(custom_files)}")

    return success, file_manifest, project_folder


def 多文件翻译(file_manifest, project_folder, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, language='en'):
    from .crazy_utils import request_gpt_model_multi_threads_with_very_awesome_ui_and_high_efficiency
    # 获取max_token_limit、max_workers和max_outline_chars，确保全局可用
    max_token_limit = get_effective_md_setting(plugin_kwargs, "MAX_TOKEN_LIMIT", MAX_TOKEN_LIMIT)
    max_workers = get_effective_md_setting(plugin_kwargs, "MAX_WORKERS", MAX_WORKERS)
    max_outline_chars = get_effective_md_setting(plugin_kwargs, "MAX_OUTLINE_CHARS", MAX_OUTLINE_CHARS)
    secondary_max_chars = get_effective_md_setting(plugin_kwargs, "SECONDARY_MAX_CHARS", 0)  # 获取secondary_max_chars参数
    
    # 获取自定义提示词，如果存在
    global PROMPT
    custom_prompt = plugin_kwargs.get("custom_translate_prompt", "")
    # 尝试从translate_prompt获取用户自定义翻译提示词
    user_translate_prompt = plugin_kwargs.get("translate_prompt", "")
    
    # 检查是否启用公式保护
    enable_formula_protection = plugin_kwargs.get("enable_formula_protection", False)
    if enable_formula_protection:
        logging.info("已启用公式保护功能")
    else:
        logging.info("未启用公式保护功能")
    
    # 优先级：用户自定义翻译提示词 > custom_translate_prompt > 默认提示词
    if user_translate_prompt and len(user_translate_prompt.strip()) > 0:
        current_prompt = user_translate_prompt.strip()
        logging.info("使用用户在界面中输入的自定义翻译提示词")
    elif custom_prompt and len(custom_prompt.strip()) > 0:
        current_prompt = custom_prompt.strip()
        logging.info("使用plugin_kwargs中的custom_translate_prompt")
    else:
        current_prompt = PROMPT
        logging.info("使用默认翻译提示词")

    # 记录提示词长度，便于调试
    logging.info(f"当前使用的提示词长度: {len(current_prompt)}")

    # 翻译前统计信息
    total_files = len(file_manifest)
    chatbot.append([f"翻译进度统计", f"待翻译文件总数: {total_files}"])
    # 添加大纲提取信息
    chatbot.append([f"大纲提取", f"将为每个书籍提取整体大纲，以增强翻译质量和术语一致性"])
    yield from update_ui(chatbot=chatbot, history=history)

    #  <-------- 读取Markdown文件，删除其中的所有注释 ---------->
    pfg = PaperFileGroup()

    # 保存原始文件列表，用于后续质量监测
    original_files = []
    translated_files = []
    
    # 保存每个线程的原文和译文片段，用于线程级质量监测
    original_fragments = []
    translated_fragments = []

    # 按文件夹分组文件
    folder_groups = {}
    for fp in file_manifest:
        folder_name = os.path.basename(os.path.dirname(fp))
        if folder_name not in folder_groups:
            folder_groups[folder_name] = []
        folder_groups[folder_name].append(fp)

    # 处理每个文件夹中的文件
    for folder_name, folder_files in folder_groups.items():
        chatbot.append([f"开始处理文件夹: {folder_name}", ""])
        yield from update_ui(chatbot=chatbot, history=history)
        
        # 为当前文件夹初始化数据
        folder_original_files = []
        folder_translated_files = []
        folder_original_fragments = []
        folder_translated_fragments = []
        # 新增：用于切片连续性检查
        md_file_slices_dict = {}
        original_texts_dict = {}

        # 当前书籍子目录完整路径
        book_folder = os.path.join(project_folder, folder_name)
        # ---------- 为当前书籍设置临时目录 ----------
        from .pdf_fns import breakdown_txt as breakdown_module
        book_temp_dir = os.path.join(book_folder, 'temp')
        os.makedirs(book_temp_dir, exist_ok=True)
        breakdown_module._current_book_temp_dir = book_temp_dir
        
        # 提取整本书的大纲
        chatbot.append([f"书籍大纲提取: {folder_name}", "正在提取整本书的大纲..."])
        yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        
        start_time = time.time()
        # 传递max_outline_chars和secondary_max_chars参数给get_book_outline
        book_outline, extraction_method = get_book_outline(book_folder, max_outline_chars=max_outline_chars, secondary_max_chars=secondary_max_chars)
        extraction_time = time.time() - start_time
        
        outline_lines = book_outline.splitlines() if book_outline else []
        chatbot.append([f"书籍大纲提取: {folder_name}", f"提取完成，共 {len(outline_lines)} 行，耗时 {extraction_time:.2f} 秒\n使用算法: {extraction_method}"])
        yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        
        # 记录大纲提取指标
        metrics_file = record_outline_metrics(book_folder, book_outline, extraction_time, extraction_method)
        if metrics_file:
            promote_file_to_downloadzone(metrics_file, chatbot=chatbot)
            chatbot.append([f"大纲指标报告", f"已生成大纲指标报告: {os.path.basename(metrics_file)}"])
            yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        
        # 使用书籍大纲增强基础提示词，传递max_outline_chars和secondary_max_chars参数
        book_enhanced_prompt = enhance_translation_prompt_with_book_outline(current_prompt, book_folder, max_outline_chars=max_outline_chars, secondary_max_chars=secondary_max_chars)

        for index, fp in enumerate(folder_files):
            # 输出统计信息
            current_file_index = file_manifest.index(fp) + 1
            chatbot.append([f"翻译进度: {current_file_index}/{total_files} ({(current_file_index/total_files*100):.1f}%)", f"开始翻译: {os.path.basename(fp)}"])
            yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
            
            pfg = PaperFileGroup()
            # 生成前缀：使用 sanitize_filename 对原始文件名进行安全化处理，并在后面加上连字符
            original_base_for_prefix = os.path.splitext(os.path.basename(fp))[0]
            prefix = sanitize_filename(original_base_for_prefix) + '-'
            with open(fp, 'r', encoding='utf-8', errors='replace') as f:
                file_content = f.read()
                # 记录删除注释后的文本
                pfg.file_paths.append(fp)
                pfg.file_contents.append(file_content)
                folder_original_files.append(fp)

            #  <-------- 拆分过长的Markdown文件 ---------->
            pfg.run_file_split(max_token_limit=max_token_limit, prefix=prefix)
            # 新增：输出切分方法到chatbot
            split_method = pfg.split_method_report[-1] if pfg.split_method_report else "未知"
            chatbot.append([f"文件 {os.path.basename(fp)} 切分方法", f"{split_method}"])
            yield from update_ui(chatbot=chatbot, history=history)
            n_split = len(pfg.sp_file_contents)
            
            # ========== 公式保护处理 ==========
            if enable_formula_protection:
                formula_protector = MathFormulaProtector()
                protected_sp_file_contents = []
                formula_mappings = []
                for slice_text in pfg.sp_file_contents:
                    protected, mapping = formula_protector.protect(slice_text)
                    protected_sp_file_contents.append(protected)
                    formula_mappings.append(mapping)
                logging.info(f"已对文件 {os.path.basename(fp)} 的切片应用公式保护")
                # 注意：此处不再提前输出掩码对照分析文件，推迟到译文生成后统一输出
            else:
                # 不使用公式保护，直接使用原始内容
                protected_sp_file_contents = pfg.sp_file_contents
                formula_mappings = [{}] * len(pfg.sp_file_contents)  # 空映射
            # ========== 切片公式修复处理 ==========
            # 检查是否启用切片公式修复功能
            enable_slice_formula_repair = plugin_kwargs.get("enable_slice_formula_repair", False) or get_effective_md_setting(plugin_kwargs, "enable_slice_formula_repair", False)
            prev_slice_unclosed_formulas_list = []  # 用于存储每个切片的上一个切片未闭合公式文本
            if enable_formula_protection and enable_slice_formula_repair:
                protected_sp_file_contents, pfg.sp_file_contents, repair_records, prev_slice_unclosed_formulas_list = apply_slice_formula_repair(
                    formula_protector, protected_sp_file_contents, pfg.sp_file_contents, fp, chatbot, plugin_kwargs, get_effective_md_setting
                )
            # ========== 结束 ==========
            
            # 保存原文片段，用于线程级质量监测
            folder_original_fragments.extend(pfg.sp_file_contents)
            # 新增：收集切片和原文
            md_file_slices_dict[os.path.basename(fp)] = pfg.sp_file_contents
            original_texts_dict[os.path.basename(fp)] = file_content

            #  <-------- 多线程翻译开始 ---------->
            if language == 'en->zh':
                # 构建发送给大语言模型的输入内容列表，添加大纲信息
                if enable_formula_protection:
                    inputs_array = []
                    for i, frag in enumerate(protected_sp_file_contents):
                        original_frag = pfg.sp_file_contents[i]
                        # 构建包含上个切片未闭合公式文本的辅助信息
                        auxiliary_info = ""
                        if enable_slice_formula_repair and prev_slice_unclosed_formulas_list and i < len(prev_slice_unclosed_formulas_list):
                            prev_unclosed = prev_slice_unclosed_formulas_list[i]
                            if prev_unclosed:
                                auxiliary_info = f"\n\n【上个切片的未闭合公式文本】\n{prev_unclosed}\n【辅助信息结束】"
                        input_with_reference = f"{book_enhanced_prompt}{auxiliary_info}\n\n这是掩码保护之前的原文，作为翻译的参考，仅供参考，不要翻译这段：\n\n{original_frag}\n\n请翻译以下带掩码的内容：\n\n{frag}"
                        inputs_array.append(input_with_reference)
                else:
                    inputs_array = [book_enhanced_prompt + f"\n\n{frag}" for frag in protected_sp_file_contents]
                inputs_show_user_array = [f"翻译 {f}" for f in pfg.sp_file_tag]
                sys_prompt_array = ["You are a professional academic paper translator." + plugin_kwargs.get("additional_prompt", "") for _ in range(n_split)]
            elif language == 'zh->en':
                # 构建发送给大语言模型的输入内容列表，添加大纲信息
                if enable_formula_protection:
                    inputs_array = []
                    for i, frag in enumerate(protected_sp_file_contents):
                        original_frag = pfg.sp_file_contents[i]
                        # 构建包含上个切片未闭合公式文本的辅助信息
                        auxiliary_info = ""
                        if enable_slice_formula_repair and prev_slice_unclosed_formulas_list and i < len(prev_slice_unclosed_formulas_list):
                            prev_unclosed = prev_slice_unclosed_formulas_list[i]
                            if prev_unclosed:
                                auxiliary_info = f"\n\n【上个切片的未闭合公式文本】\n{prev_unclosed}\n【辅助信息结束】"
                        input_with_reference = f"{book_enhanced_prompt}{auxiliary_info}\n\n这是掩码保护之前的原文，作为翻译的参考，仅供参考，不要翻译这段：\n\n{original_frag}\n\n请翻译以下带掩码的内容：\n\n{frag}"
                        inputs_array.append(input_with_reference)
                else:
                    inputs_array = [book_enhanced_prompt + f"\n\n{frag}" for frag in protected_sp_file_contents]
                inputs_show_user_array = [f"翻译 {f}" for f in pfg.sp_file_tag]
                sys_prompt_array = ["You are a professional academic paper translator." + plugin_kwargs.get("additional_prompt", "") for _ in range(n_split)]
            else:
                # 构建发送给大语言模型的输入内容列表，添加大纲信息
                if enable_formula_protection:
                    inputs_array = []
                    for i, frag in enumerate(protected_sp_file_contents):
                        original_frag = pfg.sp_file_contents[i]
                        # 构建包含上个切片未闭合公式文本的辅助信息
                        auxiliary_info = ""
                        if enable_slice_formula_repair and prev_slice_unclosed_formulas_list and i < len(prev_slice_unclosed_formulas_list):
                            prev_unclosed = prev_slice_unclosed_formulas_list[i]
                            if prev_unclosed:
                                auxiliary_info = f"\n\n【上个切片的未闭合公式文本】\n{prev_unclosed}\n【辅助信息结束】"
                        input_with_reference = f"{book_enhanced_prompt}{auxiliary_info}\n\n这是掩码保护之前的原文，作为翻译的参考，仅供参考，不要翻译这段：\n\n{original_frag}\n\n请翻译以下带掩码的内容：\n\n{frag}"
                        inputs_array.append(input_with_reference)
                else:
                    inputs_array = [book_enhanced_prompt + f"\n\n{frag}" for frag in protected_sp_file_contents]
                inputs_show_user_array = [f"翻译 {f}" for f in pfg.sp_file_tag]
                sys_prompt_array = ["You are a professional academic paper translator." + plugin_kwargs.get("additional_prompt", "") for _ in range(n_split)]
            
            # 调试：输出第一个切片的prompt内容
            if len(inputs_array) > 0:
                debug_prompt = inputs_array[0]
                logging.info(f"第一个切片的prompt长度: {len(debug_prompt)}")
                # 检查prompt中是否包含大纲信息
                if "【书籍大纲信息】" in debug_prompt:
                    logging.info("prompt中包含大纲信息")
                    # 提取大纲部分进行记录
                    outline_start = debug_prompt.find("【书籍大纲信息】")
                    outline_end = debug_prompt.find("【大纲信息结束】")
                    if outline_start != -1 and outline_end != -1:
                        outline_part = debug_prompt[outline_start:outline_end+len("【大纲信息结束】")]
                        logging.info(f"大纲部分长度: {len(outline_part)}")
                        logging.info(f"大纲部分前200字符: {outline_part[:200]}...")
                else:
                    logging.warning("prompt中未包含大纲信息")
                    logging.info(f"prompt前200字符: {debug_prompt[:200]}...")
            
            gpt_response_collection = yield from request_gpt_model_multi_threads_with_very_awesome_ui_and_high_efficiency(
                inputs_array=inputs_array,
                inputs_show_user_array=inputs_show_user_array,
                llm_kwargs=llm_kwargs,
                chatbot=chatbot,
                history_array=[[""] for _ in range(n_split)],
                sys_prompt_array=sys_prompt_array,
                max_workers=max_workers,
                scroller_max_len = 80
            )
            try:
                pfg.sp_file_result = []
                # 修改：每个md文件都输出切片调试文件，文件名加上时间戳
                temp_dir = os.path.join(os.path.dirname(fp), 'temp')
                os.makedirs(temp_dir, exist_ok=True)
                now_str = time.strftime('%Y%m%d-%H%M', time.localtime())
                out_path = os.path.join(temp_dir, f'{os.path.splitext(os.path.basename(fp))[0]}_protect_math_final_restore_marked_{now_str}.md')
                with open(out_path, 'w', encoding='utf-8') as out_f:
                    pass  # 先清空
                for i, (i_say, gpt_say) in enumerate(zip(gpt_response_collection[0::2], gpt_response_collection[1::2])):
                    if '</think>' in gpt_say:
                        gpt_say = gpt_say.split('</think>')[-1]
                    # ========== 翻译后还原公式 ==========
                    if enable_formula_protection:
                        gpt_say = formula_protector.restore(gpt_say, formula_mappings[i])
                        # ========== 修复$$公式块格式 ==========
                        gpt_say = MarkdownFixer.fix_dollar_math_blocks(gpt_say)
                    # ========== 结束 ==========
                    pfg.sp_file_result.append(gpt_say)
                    # 保存译文片段，用于线程级质量监测
                    folder_translated_fragments.append(gpt_say)
                    # 写入到temp/xxx_protect_math_final_restore_marked_时间戳.md
                    with open(out_path, 'a', encoding='utf-8') as out_f:
                        out_f.write(f'# 切片{i+1}：\n{gpt_say}\n---\n')
                # ====== 新增：译文切片合并前，去除大纲冗余行 ======
                def normalize_line(line):
                    import re
                    return re.sub(r'[^\w\u4e00-\u9fa5]+', '', line).strip().lower()
                def remove_redundant_outline_lines_in_slices(sp_file_result, outline_lines, min_match_len=2):
                    norm_outline = [normalize_line(l) for l in outline_lines if normalize_line(l)]
                    norm_outline_len = len(norm_outline)
                    new_result = []
                    deleted_blocks = {}
                    for idx, txt in enumerate(sp_file_result):
                        if idx < 3:
                            new_result.append(txt)
                            continue
                        lines = txt.splitlines()
                        norm_lines = [normalize_line(l) for l in lines]
                        to_del = set()
                        # 滑动窗口匹配
                        for win in range(min_match_len, min(10, len(lines))+1):
                            for i in range(len(lines)-win+1):
                                seg = norm_lines[i:i+win]
                                for j in range(norm_outline_len-win+1):
                                    if seg == norm_outline[j:j+win]:
                                        to_del.update(range(i, i+win))
                        # 删除这些行
                        new_lines = [l for i, l in enumerate(lines) if i not in to_del]
                        # 记录被删内容
                        if to_del:
                            if idx not in deleted_blocks:
                                deleted_blocks[idx] = []
                            deleted_blocks[idx].append('\n'.join([lines[i] for i in sorted(to_del)]))
                        new_result.append('\n'.join(new_lines))
                    return new_result, deleted_blocks
                pfg.sp_file_result, deleted_blocks_outline = remove_redundant_outline_lines_in_slices(pfg.sp_file_result, outline_lines)
                # ====== 结束 ======
                # ====== 新增：删除连续数字大纲块（仅第4个及以后切片） ======
                from .Markdown_Translate_Preoutline import remove_suspected_outline_blocks_in_slices as _remove_suspected_outline_blocks_in_slices
                def remove_suspected_outline_blocks_in_slices_with_skip(sp_file_result):
                    new_result = []
                    deleted_blocks = {}
                    for idx, txt in enumerate(sp_file_result):
                        if idx < 3:
                            new_result.append(txt)
                            continue
                        # 只对第4个及以后切片调用原算法
                        temp_result, temp_deleted = _remove_suspected_outline_blocks_in_slices([txt])
                        new_result.append(temp_result[0])
                        if temp_deleted and 0 in temp_deleted:
                            deleted_blocks[idx] = temp_deleted[0]
                    return new_result, deleted_blocks
                pfg.sp_file_result, deleted_blocks_numblock = remove_suspected_outline_blocks_in_slices_with_skip(pfg.sp_file_result)
                # ====== 结束 ======
                pfg.merge_result()
                # ====== 新增：输出被删除大纲块报告（合并两种算法） ======
                merged_deleted = {}
                for d in [deleted_blocks_outline, deleted_blocks_numblock]:
                    for idx, blocks in d.items():
                        if idx not in merged_deleted:
                            merged_deleted[idx] = []
                        merged_deleted[idx].extend(blocks)
                if merged_deleted:
                    import datetime
                    reports_dir = os.path.join(book_folder, 'reports')
                    os.makedirs(reports_dir, exist_ok=True)
                    timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
                    report_path = os.path.join(reports_dir, f'删除的大纲文本{timestamp}.md')
                    with open(report_path, 'w', encoding='utf-8') as f:
                        for idx, blocks in merged_deleted.items():
                            f.write(f'## 切片序号 {idx+1}\n')
                            for block in blocks:
                                f.write(block.strip() + '\n\n---\n')
                # ====== 结束 ======
                output_file_arr = pfg.write_result(language, prefix=prefix)
                for output_file in output_file_arr:
                    promote_file_to_downloadzone(output_file, chatbot=chatbot)
                    folder_translated_files.append(output_file)
                    if 'markdown_expected_output_path' in plugin_kwargs:
                        expected_f_name = plugin_kwargs['markdown_expected_output_path']
                        shutil.copyfile(output_file, expected_f_name)
                # 新增：所有译文生成后，再统一输出掩码对照分析文件
                if enable_formula_protection:
                    now_str = time.strftime('%Y%m%d-%H%M', time.localtime())
                    book_temp_dir = os.path.join(book_folder, 'temp')
                    os.makedirs(book_temp_dir, exist_ok=True)
                    mask_out_path = os.path.join(book_temp_dir, f'{prefix}掩码对照分析{now_str}.md')
                    with open(mask_out_path, 'a', encoding='utf-8') as f:
                        for idx, (protected, mapping) in enumerate(zip(protected_sp_file_contents, formula_mappings)):
                            f.write(f'# {os.path.basename(fp)} 切片{idx+1}：\n')
                            if mapping:
                                f.write('## 公式映射\n')
                                for k, v in mapping.items():
                                    # 修复：当掩码是 __FORMULA_BLOCK_DOLLAR_xxx__ 时，在掩码后添加换行
                                    if k.startswith('__FORMULA_BLOCK_DOLLAR_') and v.startswith('$$'):
                                        f.write(f'{k}\n{v}\n')
                                    else:
                                        f.write(f'{k}: {v}\n')
                            # 新增：插入上个切片末尾的未闭合公式文本
                            if enable_slice_formula_repair and prev_slice_unclosed_formulas_list and idx < len(prev_slice_unclosed_formulas_list):
                                prev_unclosed = prev_slice_unclosed_formulas_list[idx]
                                f.write('\n---\n')
                                f.write('### 上个切片末尾的未闭合公式文本\n')
                                if prev_unclosed and prev_unclosed.strip():
                                    f.write(prev_unclosed.strip() + '\n')
                                else:
                                    f.write('(无未闭合公式文本)\n')
                            # 新增：插入原始md文本、带掩码的原文、掩码恢复后的译文
                            f.write('\n---\n')
                            f.write('### 原始md文本\n')
                            f.write(pfg.sp_file_contents[idx] + '\n')
                            f.write('### 带掩码的原文\n')
                            f.write(protected + '\n')
                            f.write('### 掩码恢复后的译文\n')
                            if hasattr(pfg, 'sp_file_result') and len(getattr(pfg, 'sp_file_result', [])) > idx:
                                restored = formula_protector.restore(pfg.sp_file_result[idx], mapping)
                                f.write(restored + '\n')
                            else:
                                f.write('(尚未生成译文)\n')
                chatbot.append([f"{fp} 翻译成功", f"翻译完成 ({current_file_index}/{total_files}, {(current_file_index/total_files*100):.1f}%)"])
                yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
            except:
                logging.error(trimmed_format_exc())
                
        # 将当前文件夹的数据添加到总列表
        original_files.extend(folder_original_files)
        # ---------- 清除全局临时目录变量 ----------
        breakdown_module._current_book_temp_dir = None
        translated_files.extend(folder_translated_files)
        original_fragments.extend(folder_original_fragments)
        translated_fragments.extend(folder_translated_fragments)
        
        # 为当前文件夹生成报告
        if folder_original_files and folder_translated_files:
            # 调用翻译质量监测功能
            chatbot.append([f"正在为文件夹 {folder_name} 进行翻译质量监测...", "分析中..."])
            yield from update_ui(chatbot=chatbot, history=history)
            
            # 创建文件对列表，用于生成质量报告
            folder_file_pairs = list(zip(folder_original_files, folder_translated_files))
            # 新增：传递切分方法
            split_methods = pfg.split_method_report
            # 生成第一阶段的完整双语评估报告
            quality_report = generate_quality_report(
                book_folder,
                folder_file_pairs,
                folder_name,
                split_methods,
                md_file_slices_dict=md_file_slices_dict,
                original_texts_dict=original_texts_dict,
                prefix=prefix
            )
            
            # 新增：生成切片质量检查单独报告
            check2_report = write_continuity_check_report(md_file_slices_dict, original_texts_dict, folder_name, project_folder=book_folder, prefix=prefix)
            promote_file_to_downloadzone(check2_report, chatbot=chatbot)
            
            # 添加线程级翻译评估到报告中
            quality_report = evaluate_thread_translations(folder_original_fragments, folder_translated_fragments, quality_report)
            
            # 翻译完整性检查
            chatbot.append([f"正在为文件夹 {folder_name} 进行翻译完整性检查...", "分析中..."])
            yield from update_ui(chatbot=chatbot, history=history)
            completeness_report = yield from check_translation_completeness(folder_original_fragments, folder_translated_fragments, book_folder, chatbot, folder_name, llm_kwargs, prefix=prefix)
            
            chatbot.append([f"文件夹 {folder_name} 翻译质量监测完成", f"双语评估报告已生成: {os.path.basename(quality_report)}\n切片质量检查报告已生成: {os.path.basename(check2_report)}\n完整性报告已生成: {os.path.basename(completeness_report)}"])
            yield from update_ui(chatbot=chatbot, history=history)

            # ========== 新增：未启用公式保护时输出中英文对照报告 ==========
            if not enable_formula_protection:
                import datetime
                report_dir = os.path.join(book_folder, 'reports')
                os.makedirs(report_dir, exist_ok=True)
                timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
                report_path = os.path.join(report_dir, f'双语对照{timestamp}.md')
                with open(report_path, 'w', encoding='utf-8') as f:
                    for idx, (orig, trans) in enumerate(zip(folder_original_fragments, folder_translated_fragments)):
                        f.write(f'# 第{idx+1}个切片：\n')
                        f.write('### 原文：\n')
                        f.write(orig.strip() + '\n')
                        f.write('### 译文：\n')
                        f.write(trans.strip() + '\n\n')
                promote_file_to_downloadzone(report_path, chatbot=chatbot)
                chatbot.append([f"中英文对照报告已生成", f"{os.path.basename(report_path)}"])
                yield from update_ui(chatbot=chatbot, history=history)
            # ========== 结束 ==========

    #  <-------- 整理结果，退出 ---------->
    create_report_file_name = gen_time_str() + f"-chatgpt.md"
    res = write_history_to_file(gpt_response_collection, file_basename=create_report_file_name)
    promote_file_to_downloadzone(res, chatbot=chatbot)
    history = gpt_response_collection
    chatbot.append((f"所有文件翻译完成", res))
    yield from update_ui(chatbot=chatbot, history=history) # 刷新界面



    # 统计和报告功能
    subdirectories = [d for d in os.listdir(project_folder) if os.path.isdir(os.path.join(project_folder, d))]
    translated_subdirectories = []
    untranslated_subdirectories = []
    problematic_subdirectories = []
    for subdir in subdirectories:
        subdir_path = os.path.join(project_folder, subdir)
        # 这里简单假设包含翻译结果文件的目录为已翻译目录，可根据实际情况修改判断逻辑
        if any(f.endswith('-中文翻译.md') for f in os.listdir(subdir_path)):
            translated_subdirectories.append(subdir_path)
        else:
            untranslated_subdirectories.append(subdir_path)
    # 可以根据实际情况添加对有问题目录的判断逻辑
    # 生成报告文件
    report_dir = os.path.join(os.getcwd(), 'reports')
    if not os.path.exists(report_dir):
        os.makedirs(report_dir)
    
    # 生成总体统计报告
    timestamp = time.strftime("%Y%m%d%H%M%S", time.localtime())
    overall_report_file = os.path.join(report_dir, f'overall_translation_summary_{timestamp}.md')
    
    with open(overall_report_file, 'w', encoding='utf-8') as f:
        f.write('---\n')
        f.write(f'# 翻译总体统计\n')
        f.write('---\n')
        f.write(f'深度为1层的子目录个数: {len(subdirectories)}\n')
        f.write(f'翻译的子目录个数: {len(translated_subdirectories)}\n')
        f.write(f'没翻译的子目录个数: {len(untranslated_subdirectories)}\n')
        f.write('---\n')
        f.write(f'# 翻译结果明细\n')
        f.write('---\n')
        f.write('## 被翻译过的目录:\n')
        for dir_path in translated_subdirectories:
            f.write(f'- {dir_path}\n')
        f.write('## 没有被翻译的目录:\n')
        for dir_path in untranslated_subdirectories:
            f.write(f'- {dir_path}\n')
        f.write('## 其他有问题的目录:\n')
        for dir_path in problematic_subdirectories:
            f.write(f'- {dir_path}\n')
    promote_file_to_downloadzone(overall_report_file, chatbot=chatbot)
    
    

@CatchException
def Markdown英译中(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request):
    # 基本信息：功能、贡献者
    chatbot.append([
        "函数插件功能？",
        "对整个Markdown项目进行翻译。函数插件贡献者: Binary-Husky"])
    yield from update_ui(chatbot=chatbot, history=history) # 刷新界面

    # 尝试导入依赖，如果缺少依赖，则给出安装建议
    try:
        import tiktoken
    except:
        report_exception(chatbot, history,
                         a=f"解析项目: {txt}",
                         b=f"导入软件依赖失败。使用该模块需要额外依赖，安装方法```pip install --upgrade tiktoken```。")
        yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        return
    history = []    # 清空历史，以免输入溢出

    # 新增：输入区为空时自动用面板设置的默认批处理目标文件夹
    if not txt or txt.strip() == "":
        txt = get_effective_md_setting(plugin_kwargs, "DEFAULT_BATCH_FOLDER", "")

    success, file_manifest, project_folder = get_files_from_everything(txt, preference="Github")

    if not success:
        # 什么都没有
        if txt == "": txt = '空空如也的输入栏'
        report_exception(chatbot, history, a = f"解析项目: {txt}", b = f"找不到本地项目或无权访问: {txt}")
        yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        return

    if len(file_manifest) == 0:
        report_exception(chatbot, history, a = f"解析项目: {txt}", b = f"找不到任何.md文件: {txt}")
        yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        return

    # 检查是否启用公式保护
    enable_formula_protection = plugin_kwargs.get("enable_formula_protection", False)
    if enable_formula_protection:
        chatbot.append([f"翻译设置", f"已启用公式保护功能，将对数学公式进行掩码保护"])
    else:
        chatbot.append([f"翻译设置", f"未启用公式保护功能，数学公式将直接参与翻译"])
    yield from update_ui(chatbot=chatbot, history=history) # 刷新界面

    yield from 多文件翻译(file_manifest, project_folder, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, language='en->zh')





@CatchException
def Markdown中译英(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request):
    # 基本信息：功能、贡献者
    chatbot.append([
        "函数插件功能？",
        "对整个Markdown项目进行翻译。函数插件贡献者: Binary-Husky"])
    yield from update_ui(chatbot=chatbot, history=history) # 刷新界面

    # 尝试导入依赖，如果缺少依赖，则给出安装建议
    try:
        import tiktoken
    except:
        report_exception(chatbot, history,
                         a=f"解析项目: {txt}",
                         b=f"导入软件依赖失败。使用该模块需要额外依赖，安装方法```pip install --upgrade tiktoken```。")
        yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        return
    history = []    # 清空历史，以免输入溢出
    # 新增：输入区为空时自动用面板设置的默认批处理目标文件夹
    if not txt or txt.strip() == "":
        txt = get_effective_md_setting(plugin_kwargs, "DEFAULT_BATCH_FOLDER", "")
    success, file_manifest, project_folder = get_files_from_everything(txt)
    if not success:
        # 什么都没有
        if txt == "": txt = '空空如也的输入栏'
        report_exception(chatbot, history, a = f"解析项目: {txt}", b = f"找不到本地项目或无权访问: {txt}")
        yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        return
    if len(file_manifest) == 0:
        report_exception(chatbot, history, a = f"解析项目: {txt}", b = f"找不到任何.md文件: {txt}")
        yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        return

    # 在Markdown翻译指定语言等入口函数中，处理输入区为空时的默认目录逻辑
    # 例如：
    # def Markdown翻译指定语言(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request):
    #   ...
    #   if not txt or txt.strip() == "":
    #       txt = get_effective_md_setting(plugin_kwargs, "DEFAULT_BATCH_FOLDER", "")
    #   ...
    # 检查是否有用户自定义翻译提示词
    translate_prompt = plugin_kwargs.get("translate_prompt", "")
    if translate_prompt:
        logging.info(f"检测到用户自定义翻译提示词，长度: {len(translate_prompt)}")
    else:
        logging.info("未检测到用户自定义翻译提示词，将使用默认提示词")
    
    # 检查是否启用公式保护
    enable_formula_protection = plugin_kwargs.get("enable_formula_protection", False)
    if enable_formula_protection:
        chatbot.append([f"翻译设置", f"已启用公式保护功能，将对数学公式进行掩码保护"])
    else:
        chatbot.append([f"翻译设置", f"未启用公式保护功能，数学公式将直接参与翻译"])
    yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        
    yield from 多文件翻译(file_manifest, project_folder, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, language='zh->en')


@CatchException
def Markdown翻译指定语言(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request):
    # 基本信息：功能、贡献者
    chatbot.append([
        "函数插件功能？",
        "对整个Markdown项目进行翻译。函数插件贡献者: Binary-Husky"])
    yield from update_ui(chatbot=chatbot, history=history) # 刷新界面

    # 尝试导入依赖，如果缺少依赖，则给出安装建议
    try:
        import tiktoken
    except:
        report_exception(chatbot, history,
                         a=f"解析项目: {txt}",
                         b=f"导入软件依赖失败。使用该模块需要额外依赖，安装方法```pip install --upgrade tiktoken```。")
        yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        return
    history = []    # 清空历史，以免输入溢出
    # 新增：输入区为空时自动用面板设置的默认批处理目标文件夹
    if not txt or txt.strip() == "":
        txt = get_effective_md_setting(plugin_kwargs, "DEFAULT_BATCH_FOLDER", "")
    success, file_manifest, project_folder = get_files_from_everything(txt)
    if not success:
        # 什么都没有
        if txt == "": txt = '空空如也的输入栏'
        report_exception(chatbot, history, a = f"解析项目: {txt}", b = f"找不到本地项目或无权访问: {txt}")
        yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        return
    if len(file_manifest) == 0:
        report_exception(chatbot, history, a = f"解析项目: {txt}", b = f"找不到任何.md文件: {txt}")
        yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        return

    if ("advanced_arg" in plugin_kwargs) and (plugin_kwargs["advanced_arg"] == ""): plugin_kwargs.pop("advanced_arg")
    language = plugin_kwargs.get("advanced_arg", 'Chinese')
    
    # 检查是否有用户自定义翻译提示词
    translate_prompt = plugin_kwargs.get("translate_prompt", "")
    if translate_prompt:
        logging.info(f"检测到用户自定义翻译提示词，长度: {len(translate_prompt)}")
    else:
        logging.info("未检测到用户自定义翻译提示词，将使用默认提示词")
    
    # 检查是否启用公式保护
    enable_formula_protection = plugin_kwargs.get("enable_formula_protection", False)
    if enable_formula_protection:
        chatbot.append([f"翻译设置", f"已启用公式保护功能，将对数学公式进行掩码保护"])
    else:
        chatbot.append([f"翻译设置", f"未启用公式保护功能，数学公式将直接参与翻译"])
    yield from update_ui(chatbot=chatbot, history=history) # 刷新界面
        
    yield from 多文件翻译(file_manifest, project_folder, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, language=language)

def sanitize_path(path: str) -> str:
    """
    增强版路径合规处理
    """
    if not path or path in ['.', '..']:
        return os.path.normpath(path)
    
    # 预编译正则表达式提高性能
    illegal_chars = {
        "：": "_", "，": "_", "。": "_", "、": "_", "；": "_", 
        "？": "_", "！": "_", "「": "_", "」": "_", "（": "_", 
        "）": "_", "【": "_", "】": "_", "『": "_", "』": "_",
        " ": "-", "\\": "_", "/": "_", "*": "_", "?": "_", 
        "\"": "_", "<": "_", ">": "_", "|": "_", "&": "and", 
        "$": "S", "@": "at", "#": "_", "%": "_"
    }
    
    # 编译正则表达式
    pattern = re.compile('|'.join(re.escape(char) for char in illegal_chars.keys()))
    
    def replace_chars(text: str) -> str:
        return pattern.sub(lambda m: illegal_chars[m.group(0)], text)
    
    def clean_name(name: str) -> str:
        # 合并连续的特殊字符
        name = re.sub(r'[_-]{2,}', lambda m: m.group(0)[0], name)
        # 清理首尾
        return name.strip('_-')
    
    try:
        # 分离目录和文件名
        dirname, basename = os.path.split(path)
        
        # 处理目录部分
        dirname = replace_chars(dirname)
        dirname = clean_name(dirname)
        
        # 处理文件名部分
        if basename:
            name, ext = os.path.splitext(basename)
            name = replace_chars(name)
            name = clean_name(name)
            basename = f"{name}{ext}"
        
        new_path = os.path.join(dirname, basename)
        new_path = os.path.normpath(new_path)
        
        # 重命名逻辑
        if new_path != path and os.path.exists(path):
            try:
                os.rename(path, new_path)
                print(f"重命名成功: {path} -> {new_path}")
            except Exception as e:
                print(f"重命名失败 {path} -> {new_path}: {e}")
                return os.path.normpath(path)
        
        return new_path
        
    except Exception as e:
        print(f"路径处理错误 {path}: {e}")
        return os.path.normpath(path)